package com.ruoyi.wx.service.serviceImpl;

import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.wx.domain.AnnouncementWx;
import com.ruoyi.wx.domain.RedisData;
import com.ruoyi.wx.mapper.AnnouncementWxMapper;
import com.ruoyi.wx.service.IAnnouncementWxService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@Service
@Slf4j
public class AnnouncementWxServiceImpl implements IAnnouncementWxService {

    //redis key
    String key = "announce";
    //互斥锁
    String lockkey = "Mutex";

    //创建固定线程池
    private static final ExecutorService CACHE_REBUILD_EXECUTOR = Executors.newFixedThreadPool(10);

    @Autowired
    private AnnouncementWxMapper announcementWxMapper;

    @Autowired
    private RedisTemplate redisTemplate;

    //预加载到redis中
    @PostConstruct
    public void init(){
        List<AnnouncementWx> announcementWxList = announcementWxMapper.list();
        //存入redis并设置逻辑过期时间
        setWithLogicExpire(key,announcementWxList,60L,TimeUnit.SECONDS);
    }


    /**
     * 获取最新公告
     * @return
     */
    @Override
    public List<AnnouncementWx> getAnnouncement() {


        //先从redis中查数据
        String json = (String) redisTemplate.opsForValue().get(key);

        //判断是否为空
        if (json == null){
            //如果为空
            log.info("缓存没命中,什么情况");
            return null;
        }

        //命中了,json反序列化为对象
        RedisData redisData = JSONUtil.toBean(json, RedisData.class);
        //data还是json字符串
        Object data = redisData.getData();
        JSONArray jsonArray = (JSONArray) data;
        List<AnnouncementWx> announcementWxList = JSONUtil.toList(jsonArray, AnnouncementWx.class);
        //获取逻辑过期时间
        LocalDateTime expireTime = redisData.getExpireTime();

        //判断是否过期
        if (expireTime.isAfter(LocalDateTime.now())){
            //未过期,返回
            log.info("没过期,还能用");
            return announcementWxList;
        }

        //过期了,需要重建缓存
        // 获取互斥锁
        boolean isSuccess = tryLock(lockkey);
        //判断是否成功
        if (isSuccess){
            //成功
            //开启独立线程,实现缓存重建
            log.info("获取锁成功,重建数据");
           CACHE_REBUILD_EXECUTOR.submit(() ->{
                try {
                    //查数据库
                    List<AnnouncementWx> newData = announcementWxMapper.list();
                    //写入redis
                    setWithLogicExpire(key,newData,60L,TimeUnit.SECONDS);
                }catch (Exception e){
                    throw new RuntimeException(e);
                }finally {
                    unlock(lockkey);
                }
            });
        }

        //获取互斥锁失败或重建独立线程之后,返回老信息
        log.info("获取锁失败,或者正在重建信息");
        return announcementWxList;
    }


    //存入redis , 设置逻辑过期时间
    private void setWithLogicExpire(String key, Object value, Long time, TimeUnit timeUnit){
        //设置逻辑过期
        RedisData redisData = new RedisData();
        redisData.setData(value);
        redisData.setExpireTime(LocalDateTime.now().plusSeconds(timeUnit.toSeconds(time)));
        //存入redis
        redisTemplate.opsForValue().set(key,JSONUtil.toJsonStr(redisData));
    }

    //获取互斥锁
    private boolean tryLock(String key){
        Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
        return flag;
    }

    //释放锁
    private void unlock(String key){
        redisTemplate.delete(key);
    }
}
